package com.yhb.utils;

import com.yhb.conf.ResultCode;
import lombok.*;
import me.edzh.spring.bootstrap.utils.exception.ServiceException;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

public class DateUtils {
    private static SimpleDateFormat plainDateFormat = new SimpleDateFormat("yyyyMMdd");
    private static SimpleDateFormat plainTimeFormat = new SimpleDateFormat("yyyyMMddHHmmss");
    private static SimpleDateFormat plainMonthDateFormat = new SimpleDateFormat("yyyyMM");
    private static String monthStringPattern = "\\d{6}";
    private static String dateStringPattern = "\\d{8}";

    public static SimpleDateFormat noobDateFormat = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss");

    public static Calendar getFastestDay(String dayOfMonth) {
        return getFastestDay(Integer.parseInt(dayOfMonth));
    }

    /**
     * 返回离指定日期最近的具体日期
     *
     * @return
     */
    public static Calendar getFastestDay(int targetDay) {
        Calendar calendar = Calendar.getInstance();
        int todayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); // 今天日期

        int day = Math.min(calendar.getActualMaximum(Calendar.DAY_OF_MONTH), targetDay); // 本月最大日期

        if (todayOfMonth > day) {
            calendar.add(Calendar.MONTH, 1);

            day = Math.min(calendar.getActualMaximum(Calendar.DAY_OF_MONTH), targetDay); // 月数 +1 了 所以这里要刷新一下本月最大日期
        }

        calendar.set(Calendar.DAY_OF_MONTH, day);

        return calendar;
    }

    public static int daysBetweenToday(Calendar calendar) {
        if (calendar == null) return 0;
        return (int) Math.abs(LocalDate.of(calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH) + 1, // 这里血坑 月份值域是0-11 LocalDate接的是 1 - 12
                        calendar.get(Calendar.DAY_OF_MONTH)).toEpochDay() - LocalDate.now().toEpochDay());
    }
    public static int daysBetweenToday(Date date) {
        if (date == null) return 0;
        return daysBetweenToday(new Calendar.Builder().setInstant(date).build());
    }

    public static String getCurrentDateString() {
        return getDateString(new Date());
    }
    public static String getCurrentTimeString() {
        return getTimeString(new Date());
    }
    public static String getCurrentMonthString() {
        return getMonthDateString(new Date());
    }

    public static String getLastMonthString() {
        return plainMonthDateFormat.format(getLastMonthFirstDay().getTime());
    }

    public static String getYesterdayDateString() {
        return getDateString(getYesterday());
    }

    public static String getYesterdayMonthString() {
        return getMonthDateString(getYesterday());
    }

    public static Calendar getYesterday() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, -1);
        setLastTimeOfDay(calendar);
        return calendar;
    }

    public static Calendar getLastMonthFirstDay() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        return calendar;
    }

    public static Calendar calendarFromMonthString(String monthString) {
        if (!monthString.matches(monthStringPattern)) throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "month string should match /\\d{6}/");

        String yearString = monthString.substring(0, 4);
        String monthOfYearString = monthString.substring(4, 6);

        int year = Integer.parseInt(yearString);
        int month = Integer.parseInt(monthOfYearString);
        if (month > 12 || month < 1) throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "month should between 1, 12");

        var result = Calendar.getInstance();
        result.set(Calendar.YEAR, year);
        result.set(Calendar.MONTH, month - 1); // MONTH字段有效值是 0, 11 每次碰到都要注释一下
        result.set(Calendar.DAY_OF_MONTH, 1);
        setFirstTimeOfDay(result);

        return result;
    }

    public static Calendar calendarFromDateString(String dateString) {
        if (!dateString.matches(dateStringPattern)) throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "date string should match /\\d{8}/");

        int year = Integer.parseInt(dateString.substring(0, 4));
        int month = Integer.parseInt(dateString.substring(4, 6));
        int day = Integer.parseInt(dateString.substring(6, 8));

        if (month > 12 || month < 1) throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "month should between 1, 12");

        var result = Calendar.getInstance();
        result.set(Calendar.YEAR, year);
        result.set(Calendar.MONTH, month - 1); // MONTH字段有效值是 0, 11 每次碰到都要注释一下
        result.set(Calendar.DAY_OF_MONTH, day);
        setFirstTimeOfDay(result);

        return result;
    }

    public static Date getMonthLastDayLastSecond(Date date) {
        var calendar = new Calendar.Builder().setInstant(date).build();
        setLastDayOfMonth(calendar);
        setLastTimeOfDay(calendar);

        return calendar.getTime();
    }

    public static void setLastDayOfMonth(Calendar calendar) {
        int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        calendar.set(Calendar.DAY_OF_MONTH, lastDay);
    }

    public static void setFirstDayOfMonth(Calendar calendar) {
        calendar.set(Calendar.DAY_OF_MONTH, 1);
    }

    public static Date getFirstDayFirstSecondThisMonth () {
        return getFirstDayFirstSecondThisMonth(Calendar.getInstance());
    }

    public static Date getFirstDayFirstSecondThisMonth (Calendar c) {
        var cc = new Calendar.Builder().setInstant(c.getTime()).build();

        setFirstDayOfMonth(cc);
        setFirstTimeOfDay(cc);
        return cc.getTime();
    }

    public static Date getLastDayLastSecondThisMonth() {
        return getLastDayLastSecondThisMonth(Calendar.getInstance());
    }

    public static Date getLastDayLastSecondThisMonth(Calendar c) {
        var cc = new Calendar.Builder().setInstant(c.getTime()).build();
        setLastDayOfMonth(cc);
        setLastTimeOfDay(cc);
        return cc.getTime();
    }

    public static String getTimeString(Date date) {
        return plainTimeFormat.format(date);
    }

    public static String getDateString(Date date) {
        return plainDateFormat.format(date);
    }

    public static String getDateString(Calendar date) {
        return getDateString(date.getTime());
    }

    public static String getMonthDateString(Date date) {
        return plainMonthDateFormat.format(date);
    }

    public static String getMonthDateString(Calendar date) {
        return getMonthDateString(date.getTime());
    }

    public static boolean isBetween(Date date, Date min, Date max) {
        boolean higherThanMin = false;
        boolean lessThanMax = false;

        if (min != null) {
            higherThanMin = date.compareTo(min) > 0;
        } else {
            higherThanMin = true;
        }

        if (max != null) {
            lessThanMax = date.compareTo(max) < 0;
        } else {
            lessThanMax = true;
        }

        return (min != null || max != null ) && higherThanMin && lessThanMax; // 左右界必须有一个非空, 否则不是between
    }

    public static Calendar setLastTimeOfDay(Calendar c) {
        c.set(Calendar.HOUR_OF_DAY, 23);
        c.set(Calendar.MINUTE, 59);
        c.set(Calendar.SECOND, 59);
        c.set(Calendar.MILLISECOND, 999);

        return c;
    }

    public static Calendar setFirstTimeOfDay(Calendar c) {
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        return c;
    }

    public static List<MonthlyScale> getBackoffScales (Calendar moment, int backOffDays) {

        List<MonthlyScale> scales = new ArrayList<>();


        var last = new MonthlyScale();
        last.monthString = getMonthDateString(moment);
        last.startTime = setFirstTimeOfDay(moment).getTime();
        last.endTime = setLastTimeOfDay(moment).getTime();

        scales.add(last);
        for (int i = 0; i < backOffDays - 1; ++i) {
            moment.add(Calendar.DAY_OF_MONTH, -1);

            var m = DateUtils.getMonthDateString(moment);
            if (m.equals(last.getMonthString())) {
                last.setStartTime(setFirstTimeOfDay(moment).getTime());
            } else {
                last = new MonthlyScale();
                last.monthString = getMonthDateString(moment);
                last.startTime = setFirstTimeOfDay(moment).getTime();
                last.endTime = setLastTimeOfDay(moment).getTime();

                scales.add(last);
            }

        }

        return scales;
    }

    public static List<String> dateStringsBackOff(int days) {
        var res = new ArrayList<String>(days);
        var c = Calendar.getInstance();
        for (int i = 0; i < days; ++i) {
            res.add(getDateString(c));
            c.add(Calendar.DAY_OF_MONTH, -1);
        }

        return res;
    }

    public static Date nowAdd(int seconds) {
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND, seconds);
        return c.getTime();
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class MonthlyScale {
        private String monthString;
        private Date startTime;
        private Date endTime;
    }

    public static void main(String[] args) {
        System.out.println(daysBetweenToday(new Date(1599970321000L)));
    }
}
